package com.github.aloxc.plugin.restplus.common;


import com.github.aloxc.plugin.restplus.method.HttpMethod;
import com.github.aloxc.plugin.restplus.utils.JsonUtils;
import com.github.aloxc.plugin.restplus.popup.PopupBalloon;
import com.github.aloxc.plugin.restplus.test.*;
import com.github.aloxc.plugin.restplus.test.impl.RestClientResponseImpl;
import com.google.common.io.CountingInputStream;
import com.intellij.execution.ExecutionException;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.methods.*;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.ExecutorService;

import static java.util.concurrent.Executors.newFixedThreadPool;

/**
 * @author leero
 */
public class RequestHelper {
    private static final String RHINO_REGISTER = "http.client.response.handler.force.use.rhino";

    private static ExecutorService LISTENER = newFixedThreadPool(1);

    public static RestResponse request(Project project, Module module, JComponent component, RestRequest request) {
        long start = System.currentTimeMillis();
        try {
            if (!Registry.is(RHINO_REGISTER)) {

            }
            RestResponse result = doRequest(request);
            StringBuffer html = new StringBuffer()
                    .append("<div style=\"color:#FF0000;background-color:#FFAD2D;\">[")
                    .append(request.getHttpMethod())
                    .append("]<p><a href=\"" + request.getUrl() + "\"><b>")
                    .append(request.getUrl())
                    .append("</b></a>&nbsp;&nbsp;&nbsp;spend time(ms):")
                    .append((System.currentTimeMillis() - start))
                    .append("</p></div>");
            LISTENER.submit(() -> {
                request.getListenerList().stream().forEach(listener -> listener.listener(project, module, request, result, null));
            });
            return result;
        } catch (Exception ex) {
            StringBuffer html = new StringBuffer()
                    .append("<div style=\"color:#FF0000;background-color:#FFAD2D;\">[")
                    .append(request.getHttpMethod())
                    .append("]<a href=\"" + request.getUrl() + "\"><b>")
                    .append(request.getUrl())
                    .append("</b></a><br/></b>")
                    .append(ex.getMessage())
                    .append("(")
                    .append((System.currentTimeMillis() - start))
                    .append(" ms)</b></div>");
            LISTENER.execute(() -> {
                request.getListenerList().stream().forEach(listener -> listener.listener(project, module, request, null, ex.getMessage()));
            });
            PopupBalloon.showPopupBalloon(component, html.toString(), true);
        }
        return null;
    }

    private static void release(CloseableHttpResponse response, CloseableHttpClient httpClient) {
        if (response != null) {
            try {
                response.close();
            } catch (IOException e) {
            }
        }
        if (httpClient != null) {
            try {
                httpClient.close();
            } catch (IOException e) {
            }
        }
    }

    @NotNull
    private static String toString(HttpEntity entity, Charset charset) {
        String result = null;
        try {
            result = EntityUtils.toString(entity, charset == null ? StandardCharsets.UTF_8 : charset);

        } catch (IOException e) {
            e.printStackTrace();
        }
        if (result != null && JsonUtils.isJson(result)) {
            return JsonUtils.format(result);
        }
        return "";
    }

    private static RestResponse doRequest(RestRequest request) throws IOException, ExecutionException {
        HttpRequestBase method = createHttpMethod(request);
        if (request.getHttpMethod() == HttpMethod.POST_BODY) {
            StringEntity httpEntity = new StringEntity(request.getBody(),"UTF-8");
            httpEntity.setContentType("application/json");
            httpEntity.setContentEncoding("UTF-8");
            ((HttpClientRequestWithBody) method).setEntity(httpEntity);
        }
        CookieStore cookieStore = new BasicCookieStore();
        request.getCookieList().stream().forEach(it -> {
            Cookie cookie = new BasicClientCookie(it.getName(), it.getValue());
            cookieStore.addCookie(cookie);
        });

        CloseableHttpClient client = HttpClients.custom().setDefaultCookieStore(cookieStore).build();
        request.getHeaderList().stream().forEach(h -> method.addHeader(h.getName(), h.getValue()));

        CloseableHttpResponse response = client.execute(method);
        HttpEntity entity = response.getEntity();
        InputStream body = (entity == null) ? null : entity.getContent();

        CountingInputStream countingInputStream = new CountingInputStream(body);
        Charset charset = getResponseCharset(entity);
        InputStreamReader reader = new InputStreamReader(countingInputStream, charset);

        char[] b = new char[4096];
        int len;
        StringBuffer buf = new StringBuffer();
        while ((len = reader.read(b)) != -1) {
            String out = new String(b, 0, len);
            buf.append((charset == StandardCharsets.UTF_8) ? StringUtil.replaceUnicodeEscapeSequences(out) : out);
        }
        RestClientResponse clientResponse = RestClientResponseImpl.createResponse(response, buf);
        RestResponse restResponse = RestResponse.create(clientResponse);

        HttpClientResponseHandlerExecutor executor = HttpResponseHandlerNashornExecutor.create();
        restResponse.setContent(buf.toString());
        HttpResponseExecutionResult result = executor.execute(restResponse, clientResponse, request.getScript());
        List<HttpClientTest> testList = result.getTestList();
        List<Object> printList = result.getPrintList();
        List<TestResult> testResultList = HttpClientTestsRunner.runTests(testList);
        restResponse.setPrintList(printList);
        restResponse.setTestResultList(testResultList);
        try {
            executor.close();
            response.close();
        } catch (Exception eee) {
        }
        return restResponse;
    }

    private static Charset getResponseCharset(@NotNull HttpEntity entity) {
        ContentType contentType = ContentType.getOrDefault(entity);
        if (contentType.getCharset() == null) {
            contentType = ContentType.getByMimeType(contentType.getMimeType());
        }

        Charset charset = (contentType != null) ? contentType.getCharset() : null;
        if (((charset != null) ? charset : HTTP.DEF_CONTENT_CHARSET) == null) {
            return null;
        }
        return (charset != null) ? charset : HTTP.DEF_CONTENT_CHARSET;
    }


    private static Long fromString(String num) {
        try {
            return new Long(num);
        } catch (Exception e) {
            return null;
        }
    }

    private static HttpRequestBase createHttpMethod(RestRequest request) {
        String uri = request.getUrl();
        if (HttpMethod.GET == request.getHttpMethod()) {
            return new HttpGet(uri);
        }
        if (HttpMethod.HEAD == request.getHttpMethod()) {
            return new HttpHead(uri);
        }
        if (HttpMethod.DELETE == request.getHttpMethod()) {
            return new HttpDelete(uri);
        }
        if (HttpMethod.OPTIONS == request.getHttpMethod()) {
            return new HttpOptions(uri);
        }
        if (HttpMethod.PUT == request.getHttpMethod()) {
            return new HttpPut(uri);
        }
        if (HttpMethod.PATCH == request.getHttpMethod()) {
            return new HttpPatch(uri);
        }
        if (HttpMethod.TRACE == request.getHttpMethod()) {
            return new HttpTrace(uri);
        }
        if (HttpMethod.POST == request.getHttpMethod()) {
            return new HttpPost(uri);
        }
        if (HttpMethod.POST_BODY == request.getHttpMethod()) {
            return new HttpClientRequestWithBody(request.getHttpMethod(), uri);
        }
        return null;
    }
}
